/*

gcc pc.c -lpthread && while true; do ./a.out; sleep 1; done

*/
#include <assert.h>
#include <stdio.h>
#include <pthread.h>
#define LEN 4
#define TURN LEN*2

char buf[LEN];
int head, tail, convert;

#define IN(c)   {   buf[head++] = (c);      \
                    head %= LEN;            }

typedef struct sema{
    int value;
    pthread_mutex_t mutex;
    pthread_cond_t cond;
}sema;

sema mutex, prod, comp, cons;

void sema_init(sema* s, int value)
{
    s->value = value;
    pthread_cond_init(&s->cond, NULL);
    pthread_mutex_init(&s->mutex, NULL);
}

void P(sema* s)
{
    pthread_mutex_lock(&s->mutex);
    while (s->value <= 0)
        pthread_cond_wait(&s->cond, &s->mutex);

    s->value--;
    pthread_mutex_unlock(&s->mutex);
}

void V(sema* s)
{
    pthread_mutex_lock(&s->mutex);
    pthread_cond_signal(&s->cond);
    s->value++;
    pthread_mutex_unlock(&s->mutex);
}

char OUT()
{
    char tmp = buf[tail++];
    tail %= LEN;          
    return tmp;    
}

char CONVERT()
{   
    buf[convert] -= 'a'-'A';
    char tmp = buf[convert++];
    convert %= LEN;                
    return tmp;
}

void* produce(void* arg)
{
    for (int i = 0; i < TURN; i++)
    {
        P(&prod);
        P(&mutex);

        IN('a'+i);
        printf("%c\n", 'a'+i);

        V(&mutex);
        V(&comp);
    }
    return NULL;
}

void* compute(void* arg)
{
    for (int i = 0; i < TURN; i++)
    {
        P(&comp);
        P(&mutex);

        printf("   %c:", buf[convert]);
        printf("%c\n", CONVERT());
        
        V(&mutex);
        V(&cons);
    }
    return NULL;
}

void* consume(void* arg)
{
    for (int i = 0; i < TURN; i++)
    {
        P(&cons);
        P(&mutex);
        
        printf("        %c\n", OUT());

        V(&mutex);
        V(&prod);
    }
    return NULL;
}

int main(int argc, char** argv)
{
    sema_init(&mutex, 1);
    sema_init(&prod, 3);
    sema_init(&comp, 0);
    sema_init(&cons, 0);

    pthread_t pid[3];
    pthread_create(pid, NULL, produce, NULL);
    pthread_create(pid+1, NULL, compute, NULL);
    pthread_create(pid+2, NULL, consume, NULL);

    pthread_join(pid[0], NULL);
    pthread_join(pid[1], NULL);
    pthread_join(pid[2], NULL);

    printf("\n-----------------------------------------------\n\n");
}